package timer

import (
	"fmt"
	"time"
	"sync/atomic"
	"container/list"
)

type Timer struct {
	ID          uint64
	callback    func()
	delay       time.Duration
	count 		uint
}

var (
	timerId     uint64
	queues  	map[int64]*list.List
	timers      map[uint64]*Timer
)

func currentTime() int64 {
	return time.Now().UnixNano() / 1000000
}

func (timer *Timer) delayTime() int64 {

	delay := timer.delay.Nanoseconds() / 1000000
	if delay < 1 {
		return time.Millisecond.Nanoseconds() / 1000000
	}

	return delay
}

func init() {
	timerId = 0
	queues  = make(map[int64]*list.List)
	timers  = make(map[uint64]*Timer)

	go run()
}

func tryError()  {
	if err := recover(); err != nil {
		fmt.Println("timer error:", err)
	}
}

func run() {
	defer tryError()

	sleep := time.NewTimer(time.Millisecond)

	for {
		select {
		case <-sleep.C:
			sleep.Reset(time.Millisecond)
			nt := currentTime()

			if qs, ok := queues[nt]; ok {
				for v := qs.Front(); v != nil; v = v.Next() {
					go runTimer(v.Value.(uint64))
				}
			}

			delete(queues, nt)
		}
	}
}

func runTimer(timerID uint64) {
	defer tryError()

	if timer, ok := timers[timerID]; ok {
		timer.callback()

		if timer.count <= 0 {
			push(timer)
			return
		}

		timer.count -= 1

		if timer.count == 0 {
			delete(timers, timerID)
		} else {
			push(timer)
		}
	}
}

func Tick(delay time.Duration, callback func()) (timerID uint64) {
	return Do(delay,callback, 0)
}

func After(delay time.Duration, callback func()) (timerID uint64) {
	return Do(delay, callback, 1)
}

func Del(timerID uint64) {
	if _, ok := timers[timerID]; ok {
		delete(timers, timerID)
	}
}

func Get(timerID uint64) *Timer {
	if timer, ok := timers[timerID]; ok {
		return timer
	}
	return nil
}

func Do(delay time.Duration, callback func(), count uint) (timerID uint64) {

	if delay == 0 {
		callback()
		return 0
	}

	timerID = atomic.AddUint64(&timerId, 1)

	timer := &Timer{
		ID:			timerID,
		callback:   callback,
		delay:      delay,
		count: 		count,
	}

	timers[timer.ID] = timer

	push(timer)

	return timerID
}

func push(timer *Timer) {
	nt := currentTime() + timer.delayTime()

	timers, ok := queues[nt]
	if !ok {
		timers = list.New()
	}

	timers.PushBack(timer.ID)

	queues[nt] = timers
}